/*
 * net.cpp
 *
 *  Created on: Apr 3, 2014
 *      Author: killerg
 */


#include "net.h"


#include <assert.h>
#include <list>
#include <map>
#include <set>
using namespace std;

#include "share/net_buffer.h"
#include "share/cs_msg.pb.h"
#include "share/util.h"

#include <errno.h>
#include <event2/event.h>
#include <event2/bufferevent.h>
#include <event2/listener.h>
#include <event2/util.h>

struct listener_t {
	evconnlistener* listener = NULL;
	conn_disconnect_cb_t disconnectcb = NULL;
	conn_data_cb_t datacb = NULL;
	conn_accept_cb_t acceptcb = NULL;
};

template<typename MsgT>
bool conn_send(conn_t* conn, MsgT* &msg) {
	if (conn->shutting)
		return false;

	return evbuffer_send<MsgT>(bufferevent_get_output(conn->bev), msg);
}

size_t conn_send(conn_t* conn, void* msg, size_t size) {
	if (conn->shutting)
		return false;

	return bufferevent_write(conn->bev, msg, size);
}

void conn_disconnect(conn_t* conn) {
	if (conn->shutting)
		return;

	if (evbuffer_get_length(bufferevent_get_output(conn->bev)) > 0)
		conn->shutting = 1;
	else
		shutdown(bufferevent_getfd(conn->bev), SHUT_RDWR);
}

static void bev_s_read_cb(bufferevent* bev, void* ctx)
{
//	printf("bev_read_cb input:%d\n", evbuffer_get_length(bufferevent_get_input(bev)));
	conn_t* conn = (conn_t*)ctx;
	conn->listener->datacb(conn);
}

static void bev_s_write_cb(bufferevent* bev, void* ctx)
{
//	printf("bev_write_cb output:%d\n", evbuffer_get_length(bufferevent_get_output(bev)));
	conn_t* conn = (conn_t*)ctx;
	if (conn->shutting && evbuffer_get_length(bufferevent_get_output(conn->bev)) == 0)
		shutdown(bufferevent_getfd(conn->bev), SHUT_RDWR);
}

static void bev_s_event_cb(bufferevent* bev, short events, void* ctx)
{
//	printf("bev_event_cb events:%d\n", events);
	conn_t* conn = (conn_t*)ctx;
	if (events & BEV_EVENT_ERROR) {
		evutil_socket_t sock = bufferevent_getfd(bev);
		int err = evutil_socket_geterror(sock);
		if (err && err != ECONNRESET)
			printf("Error from bufferevent: %s\n", evutil_socket_error_to_string(err));

		conn->listener->disconnectcb(conn);
		bufferevent_free(conn->bev);
		delete conn;
		conn == NULL;
	}
	if (events & BEV_EVENT_EOF) {
		conn->listener->disconnectcb(conn);
		bufferevent_free(conn->bev);
		delete conn;
		conn == NULL;
	}
}

static void accept_conn_cb(struct evconnlistener *evlistener,
						evutil_socket_t fd, struct sockaddr *address, int socklen,
						void *ctx)
{
	struct event_base *base = evconnlistener_get_base(evlistener);
	struct bufferevent *bev = bufferevent_socket_new(base, fd, BEV_OPT_CLOSE_ON_FREE);

	auto listener = (listener_t*)ctx;
	conn_t* conn = new conn_t;
	conn->bev = bev;
	conn->listener = listener;
	conn->shutting = 0;
	conn->eof = 0;

	bufferevent_setcb(bev, bev_s_read_cb, bev_s_write_cb, bev_s_event_cb, conn);
	bufferevent_enable(bev, EV_READ|EV_WRITE|EV_ET);
	listener->acceptcb(conn);
}

static void accept_error_cb(struct evconnlistener *listener, void *ctx)
{
	struct event_base *base = evconnlistener_get_base(listener);
	int err = EVUTIL_SOCKET_ERROR();
	fprintf(stderr, "Got an error %d (%s) on the listener. "
			"Shutting down.\n", err, evutil_socket_error_to_string(err));

	switch (err) {
	case EMFILE:
	case ENFILE:
		break;
	default:
		event_base_loopexit(base, NULL);
		break;
	}
}

listener_t* start_listen(
		event_base* base,
		const char* ip,
		const uint16_t port,
		conn_accept_cb_t acceptcb,
		conn_disconnect_cb_t disconnectcb,
		conn_data_cb_t datacb)
{
    sockaddr addr;
    int addr_len = sizeof(addr);
    if (evutil_parse_sockaddr_port(fmt_cstr("%s:%d", ip, port), &addr, &addr_len) < 0) {
        return NULL;
    }
    auto listener = new listener_t;
    listener->datacb = datacb;
    listener->acceptcb = acceptcb;
    listener->disconnectcb = disconnectcb;

    listener->listener = evconnlistener_new_bind(
			base, accept_conn_cb, listener,
			LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE,
			-1, &addr, sizeof(addr));
    if (!listener->listener) {
		perror("Couldn't create listener");
		delete listener;
		return NULL;
    }

    evconnlistener_set_error_cb(listener->listener, accept_error_cb);

    return listener;
}

void stop_listen(listener_t* listener)
{
	evconnlistener_free(listener->listener);
	delete listener;
}

struct connector_t {
	conn_connect_cb_t connectcb = NULL;
	conn_disconnect_cb_t disconnectcb = NULL;
	conn_data_cb_t datacb = NULL;
	int8_t connected:1;
};


static void bev_c_read_cb(bufferevent* bev, void* ctx)
{
//	printf("bev_read_cb input:%d\n", evbuffer_get_length(bufferevent_get_input(bev)));
	conn_t* conn = (conn_t*)ctx;
	conn->connector->datacb(conn);
}

static void bev_c_write_cb(bufferevent* bev, void* ctx)
{
//	printf("bev_write_cb output:%d\n", evbuffer_get_length(bufferevent_get_output(bev)));
	conn_t* conn = (conn_t*)ctx;
	if (conn->shutting && evbuffer_get_length(bufferevent_get_output(conn->bev)) == 0)
		shutdown(bufferevent_getfd(conn->bev), SHUT_RDWR);
}

static void bev_c_event_cb(bufferevent* bev, short events, void* ctx)
{
//	printf("bev_event_cb events:%d\n", events);
	conn_t* conn = (conn_t*)ctx;
	if (events & BEV_EVENT_CONNECTED) {
		conn->connector->connected = 1;
		conn->connector->connectcb(conn);
		return;
	}

	if (events & BEV_EVENT_ERROR) {
		evutil_socket_t sock = bufferevent_getfd(bev);
		int err = evutil_socket_geterror(sock);
		if (err && err != ECONNRESET)
			printf("Error from bufferevent: %s\n", evutil_socket_error_to_string(err));

		if (conn->connector->connected) {
			conn->connector->disconnectcb(conn);
			conn->connector->connected = 0;
		}
		bufferevent_free(conn->bev);
		delete conn->connector;
		delete conn;
		conn == NULL;
		return;
	}
	if (events & BEV_EVENT_EOF) {
		if (conn->connector->connected) {
			conn->connector->disconnectcb(conn);
			conn->connector->connected = 0;
		}
		bufferevent_free(conn->bev);
		delete conn->connector;
		delete conn;
		conn == NULL;
		return;
	}
}

conn_t* start_connect(
		event_base* base,
		evdns_base* dns,
		const char* addr,
		short port,
		conn_connect_cb_t connectcb,
		conn_disconnect_cb_t disconnectcb,
		conn_data_cb_t datacb)
{
	auto conn = new conn_t;
	conn->bev = bufferevent_socket_new(base, -1, BEV_OPT_CLOSE_ON_FREE);
	conn->connector = new connector_t;
	conn->eof = 0;
	conn->shutting = 0;

	conn->connector->connectcb = connectcb;
	conn->connector->disconnectcb = disconnectcb;
	conn->connector->datacb = datacb;
	conn->connector->connected = 0;

	bufferevent_setcb(conn->bev, bev_c_read_cb, bev_c_write_cb, bev_c_event_cb, conn);
	bufferevent_enable(conn->bev, EV_READ|EV_WRITE|EV_ET);

	if (bufferevent_socket_connect_hostname(conn->bev, dns, AF_UNSPEC, addr, port) != 0) {
		bufferevent_free(conn->bev);
		delete conn->connector;
		delete conn;
		conn = NULL;
	}
	return conn;
}
